﻿#region Header

/*
Remote views based on linq
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using InterLinq.Base;
using InterLinq.Client;
using InterLinq.Objects;
using InterLinq.Test.PD;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sebarf.Core;
using Sebarf.Core.Interfaces;
using Sebarf.Diagnostics.Interfaces;
using Sebarf.Server;
using Sebastof.Services.Interfaces;

#endregion

namespace InterLinq.Test {
	/// <summary>
	/// This class tests if everything is wokring when having concurrency aspects
	/// </summary>
	[TestClass]
	public class TestConcurrency {
		#region Public Methods

		private bool stop;

		[TestMethod]
		[DeploymentItem("TestSampleApplication.RequestHandler.Client.Internal.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Client.Application.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Server.Internal.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Server.Application.xaml")]
		public void TestVersioning() {
			// create sample data
			Dictionary<Type, IList> data = SampleDataFactory.CreateInterLinqOrdersWithContent(2, 10);

			//create a object query handler based on our data container
			var queryHandler = new ObjectQueryHandler(new ObjectBasedDataSource(data));

			//create the server
			IServer server = InterLinqServer.GetSebarfServerForInterLinq(queryHandler, queryHandler,
																		  "TestSampleApplication.RequestHandler.Server.Internal.xaml",
																		  "TestSampleApplication.RequestHandler.Server.Application.xaml",
																		  "net.tcp://localhost:9876/InterLinqService");
			try {
				server.Start();

				// create client 1
				ClientContext ctx1;
				IServiceLocatorService serviceLocator1;
				GetClient(out ctx1, out serviceLocator1);

				// create client 2
				ClientContext ctx2;
				IServiceLocatorService serviceLocator2;
				GetClient(out ctx2, out serviceLocator2);

				ClientContext ctx3;
				IServiceLocatorService serviceLocator3;
				GetClient(out ctx3, out serviceLocator3);

				IProduct o1 = (from o in ctx1.GetData<IProduct>() select o).First();
				IProduct o2 = (from o in ctx2.GetData<IProduct>() select o).First();
				IProduct o3 = (from o in ctx3.GetData<IProduct>() select o).First();

				o1.Name = "Changed by CTX1";
				o2.Name = "Changed by CTX2";

				serviceLocator1.GetRegisteredService<IChangeHandlerService>().SubmitChanges();
				o1.Name = "Changed by CTX1 2nd time";
				serviceLocator1.GetRegisteredService<IChangeHandlerService>().SubmitChanges();

				try {
					serviceLocator2.GetRegisteredService<IChangeHandlerService>().SubmitChanges();
					Assert.Fail("Submit a change of a request already change in the meantime should throw an exception");
				}
				catch (Exception err) {
					Assert.IsTrue(err.Message == "Version Conflict! Can not update object.");

					try {
						serviceLocator3.GetRegisteredService<IChangeHandlerService>().Refresh(o3);
						o3.Name = "Changed by CTX3";
						serviceLocator3.GetRegisteredService<IChangeHandlerService>().SubmitChanges();
					}
					catch (Exception errInner) {
						Assert.Fail(errInner.Message);
					}
				}
			}
			finally {
				server.Stop();
			}
		}

		private static void GetClient(out ClientContext ctx1, out IServiceLocatorService serviceLocator1) {
			IClient client1;

			InterLinqClientFactory.CreateSebarfClientForInterlinq("TestSampleApplication.RequestHandler.Client.Internal.xaml",
																   "CacheBasedLazyLoadProfile_Server.xaml",
																   "CacheBasedLazyLoadProfile.xaml",
																   "Client with id:" + Guid.NewGuid(),
																   "net.tcp://localhost:9876/InterLinqService", out client1,
																   out ctx1, out serviceLocator1);
		}

		[TestMethod]
		[DeploymentItem("TestSampleApplication.RequestHandler.Client.Internal.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Client.Application.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Server.Internal.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Server.Application.xaml")]
		public void TestConflictResolution() {
			// create sample data
			Dictionary<Type, IList> data = SampleDataFactory.CreateInterLinqOrdersWithContent(2, 10);

			//create a object query handler based on our data container
			var queryHandler = new ObjectQueryHandler(new ObjectBasedDataSource(data));

			//create the server
			IServer server = InterLinqServer.GetSebarfServerForInterLinq(queryHandler, queryHandler,
																		  "TestSampleApplication.RequestHandler.Server.Internal.xaml",
																		  "TestSampleApplication.RequestHandler.Server.Application.xaml",
																		  "net.tcp://localhost:9876/InterLinqService");
			try {
				server.Start();

				// create client 1
				IClient client1;
				ClientContext ctx1;
				IServiceLocatorService serviceLocator1;

				InterLinqClientFactory.CreateSebarfClientForInterlinq("TestSampleApplication.RequestHandler.Client.Internal.xaml",
																	   "CacheBasedLazyLoadProfile_Server.xaml",
																	   "CacheBasedLazyLoadProfile.xaml",
																	   "Client with id:" + Guid.NewGuid(),
																	   "net.tcp://localhost:9876/InterLinqService", out client1,
																	   out ctx1, out serviceLocator1);

				// create client 2
				IClient client2;
				ClientContext ctx2;
				IServiceLocatorService serviceLocator2;

				InterLinqClientFactory.CreateSebarfClientForInterlinq("TestSampleApplication.RequestHandler.Client.Internal.xaml",
																	   "CacheBasedLazyLoadProfile_Server.xaml",
																	   "CacheBasedLazyLoadProfile.xaml",
																	   "Client with id:" + Guid.NewGuid(),
																	   "net.tcp://localhost:9876/InterLinqService", out client2,
																	   out ctx2, out serviceLocator2);

				EAction conflictClientActionClient1 = EAction.CommitChanges;
				object conflictClient1VersionClient1 = "";
				object conflictClient1VersionServer1 = "";

				EAction conflictClientActionClient2 = EAction.CommitChanges;
				object conflictClient1VersionClient2 = "";
				object conflictClient1VersionServer2 = "";

				serviceLocator1.GetRegisteredService<IChangeHandlerService>().ConflictDetectionHandler +=
					delegate(EAction oncurredByDoingAction, object clientVersion, object serverVersion, out object resolvedObject) {
						conflictClient1VersionClient1 = (clientVersion as IProduct).Name;
						conflictClient1VersionServer1 = (serverVersion as IProduct).Name;
						conflictClientActionClient1 = oncurredByDoingAction;
						resolvedObject = serverVersion;
					};

				serviceLocator2.GetRegisteredService<IChangeHandlerService>().ConflictDetectionHandler +=
					delegate(EAction oncurredByDoingAction, object clientVersion, object serverVersion, out object resolvedObject) {
						conflictClient1VersionClient2 = (clientVersion as IProduct).Name;
						conflictClient1VersionServer2 = (serverVersion as IProduct).Name;
						conflictClientActionClient2 = oncurredByDoingAction;
						resolvedObject = serverVersion;
					};

				// get objects from both clients
				IProduct product1Client1 = ctx1.GetData<IProduct>().FirstOrDefault();
				IProduct product1Client2 = ctx2.GetData<IProduct>().FirstOrDefault();

				product1Client1.Name = "ChangedOnClient1";
				product1Client2.Name = "ChangedOnClient2";

				Assert.IsNull(conflictClient1VersionClient1);
				Assert.IsNull(conflictClient1VersionServer1);
				Assert.IsNull(conflictClient1VersionClient2);
				Assert.IsNull(conflictClient1VersionServer2);

				serviceLocator1.GetRegisteredService<IChangeHandlerService>().SubmitChanges();
				serviceLocator2.GetRegisteredService<IChangeHandlerService>().SubmitChanges();
				// detect conflict on commit
				Assert.IsTrue(conflictClient1VersionClient2.ToString() == "ChangedOnClient2");
				Assert.IsTrue(conflictClient1VersionServer2.ToString() == "ChangedOnClient1");
				Assert.IsTrue(conflictClientActionClient1 == EAction.CommitChanges);
				// server version is used
				Assert.IsTrue(product1Client2.Name == "ChangedOnClient1");
				product1Client2.Name = "ChangedOnClient2_Version2";
				// refresh object
				serviceLocator2.GetRegisteredService<IChangeHandlerService>().Refresh(product1Client2);
				// conflict detected, server version is used
				Assert.IsTrue(conflictClient1VersionClient2.ToString() == "ChangedOnClient2_Version2");
				Assert.IsTrue(conflictClient1VersionServer2.ToString() == "ChangedOnClient1");
				Assert.IsTrue(conflictClientActionClient1 == EAction.ReloadObject);
				Assert.IsTrue(product1Client2.Name == "ChangedOnClient1");

				// check newer version exits
				Assert.IsFalse(
					serviceLocator1.GetRegisteredService<IChangeHandlerService>().IsThereaNewVersionAvailableForThisObject(
						product1Client1));

				// save object on client 2
				product1Client2.Name = "ChangedOnClient2_Version3";
				serviceLocator2.GetRegisteredService<IChangeHandlerService>().SubmitChanges();

				// check newer version exits
				Assert.IsFalse(
					serviceLocator1.GetRegisteredService<IChangeHandlerService>().IsThereaNewVersionAvailableForThisObject(
						product1Client1));
			}
			finally {
				server.Stop();
			}
		}

		[TestMethod]
		[DeploymentItem("TestSampleApplication.RequestHandler.Client.Internal.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Client.Application.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Server.Internal.xaml")]
		[DeploymentItem("TestSampleApplication.RequestHandler.Server.Application.xaml")]
		public void RunRaceConditions() {
			// create sample data
			Dictionary<Type, IList> data = SampleDataFactory.CreateInterLinqOrdersWithContent(2, 10);

			//create a object query handler based on our data container
			var queryHandler = new ObjectQueryHandler(new ObjectBasedDataSource(data));

			//create the server
			IServer server = InterLinqServer.GetSebarfServerForInterLinq(queryHandler, queryHandler,
																		  "TestSampleApplication.RequestHandler.Server.Internal.xaml",
																		  "TestSampleApplication.RequestHandler.Server.Application.xaml",
																		  "net.tcp://localhost:9876/InterLinqService");
			try {
				server.Start();

				foreach (IProduct entry in data[typeof(IProduct)]) {
					entry.Name = "Product";
				}

				Thread writerThread1 = GetWriterThread(200, 5);
				Thread writerThread2 = GetWriterThread(150, 2);
				Thread writerThread3 = GetWriterThread(150, 3);
				var writers = new List<Thread>();
				for (int i = 0; i < 10; i++) {
					writers.Add(GetWriterThread(100, 100));
				}

				Thread readerThread1 = GetReaderThread();
				Thread readerThread2 = GetReaderThread();
				Thread readerThread3 = GetReaderThread();

				//readerThread1.Start();
				writerThread1.Start();
				//readerThread2.Start();
				writerThread2.Start();
				//readerThread3.Start();
				writerThread3.Start();

				foreach (Thread w in writers) {
					w.Start();
					Thread.Sleep(100);
				}

				Thread.Sleep(200000);
				stop = true;
			}
			finally {
				server.Stop();
			}
		}

		private Thread GetWriterThread(int c1, int c2) {
			var writerThread1 = new Thread(delegate() {
				// create client 1
				IClient client1;
				ClientContext ctx1;
				IServiceLocatorService serviceLocator1;

				InterLinqClientFactory.CreateSebarfClientForInterlinq(
					"TestSampleApplication.RequestHandler.Client.Internal.xaml",
					"CachelessProfile_Server.xaml", "CachelessProfile.xaml",
					"Client with id:" + Guid.NewGuid(), "net.tcp://localhost:9876/InterLinqService",
					out client1, out ctx1, out serviceLocator1);

				while (!stop) {
					string newName = Guid.NewGuid().ToString();
					IProduct[] products = ctx1.GetData<IProduct>().ToArray();
					foreach (IProduct p in products) {
						p.Name = newName;
					}
					if (products.Length > c1) {
						for (int i = 0; i < 30; i++) {
							serviceLocator1.GetRegisteredService<IChangeHandlerService>().DeleteOnSubmit(
								products[i]);
						}
					}
					else {
						for (int i = 0; i < c2; i++) {
							serviceLocator1.GetRegisteredService<IChangeHandlerService>().InsertOnSubmit(
								new InterLinqProduct(newName, 10));
						}
					}
					serviceLocator1.GetRegisteredService<IChangeHandlerService>().SubmitChanges();
				}
			});
			return writerThread1;
		}

		private Thread GetReaderThread() {
			var t = new Thread(delegate() {
				// create client 2
				IClient client;
				ClientContext ctx;
				IServiceLocatorService serviceLocator;

				InterLinqClientFactory.CreateSebarfClientForInterlinq(
					"TestSampleApplication.RequestHandler.Client.Internal.xaml", "CachelessProfile_Server.xaml",
					"CachelessProfile.xaml", "Client with id:" + Guid.NewGuid(),
					"net.tcp://localhost:9876/InterLinqService", out client, out ctx, out serviceLocator);

				while (!stop) {
					IProduct[] products = ctx.GetData<IProduct>().ToArray();
					string newName = string.Empty;
					foreach (IProduct p in products) {
						if (string.IsNullOrEmpty(newName)) {
							newName = p.Name;
						}
						else {
							Assert.AreEqual(p.Name, newName);
						}
					}
					Logger.WriteInformation("All objects are set to:" + newName);
				}
			});
			return t;
		}

		#endregion
	}
}